package com.wiscamp.ninechapters.tags.domain.services;

import com.wiscamp.ninechapters.tags.domain.TagsErrorCodes;
import com.wiscamp.ninechapters.tags.domain.models.KnowledgePoint;
import com.wiscamp.ninechapters.tags.domain.models.KnowledgePointRelation;
import com.wiscamp.ninechapters.tags.domain.repositories.KnowledgePointRepository;
import com.wiscamp.ninechapters.tags.enums.PointRelationTypes;
import cube.common.cache.CacheManagerNames;
import cube.ddd.aspect.HandleServiceException;
import cube.ddd.domain.BaseDomainService;
import cube.ddd.exception.ExceptionUtils;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;

/**
 * Subject Point Service
 *
 * @author yifeng
 */
@Service
public class KnowledgePointService extends BaseDomainService {

    private final KnowledgePointRepository knowledgePointRepository;

    private final String CACHE_NAME = "knowledgePoints";

    public KnowledgePointService(KnowledgePointRepository knowledgePointRepository) {
        this.knowledgePointRepository = knowledgePointRepository;
    }


    // region knowledgePoint
    @Cacheable(value = CACHE_NAME, key = "#pointId", cacheManager = CacheManagerNames.SIXTY_MINUTES_CACHE)
    @HandleServiceException
    public KnowledgePoint getKnowledgePoint(@Min(0) long pointId) {
        var point = knowledgePointRepository.getKnowledgePoint(pointId);
        if (Objects.isNull(point)) {
            ExceptionUtils.throwServiceException(TagsErrorCodes.POINT_NOT_FOUND, pointId);
        }
        return point;
    }

    /*@HandleServiceException
    public KnowledgePoint getKnowledgePointByCode(@NotBlank String code, @Min(0) long subjectId) {
        return knowledgePointRepository.getKnowledgePointByCode(code, subjectId);
    }

    @HandleServiceException
    public Collection<KnowledgePoint> getKnowledgePointsByName(@NotBlank String name, @Min(0) long subjectId) {
        return knowledgePointRepository.getKnowledgePointsByName(name, subjectId);
    }

    @HandleServiceException
    public Collection<KnowledgePoint> getKnowledgePointsByNameContains(@NotBlank String name, @Min(0) long subjectId) {
        return knowledgePointRepository.getKnowledgePointsByNameContains(name, subjectId);
    }

    @HandleServiceException
    public KnowledgePoint getFirstKnowledgePointByName(@NotBlank String name, @Min(0) long subjectId) {
        return knowledgePointRepository.getFirstKnowledgePointByName(name, subjectId);
    }

    @HandleServiceException
    public Collection<KnowledgePoint> getKnowledgePointsByIds(@NotBlank Collection<Long> pointIds) {
        return knowledgePointRepository.getKnowledgePointsByIds(pointIds);
    }

    @HandleServiceException
    public Collection<KnowledgePoint> getKnowledgePointsBySubject(@Min(0) long subjectId) {
        return knowledgePointRepository.getKnowledgePointsBySubject(subjectId);
    }

    @HandleServiceException
    public Collection<Long> getKnowledgePointIdsBySubject(@Min(0) long subjectId) {
        var points = knowledgePointRepository.getKnowledgePointsBySubject(subjectId);
        return points.stream().map(KnowledgePoint::getPointId).collect(Collectors.toList());
    }

    @HandleServiceException
    public Collection<Long> getKnowledgePointIdsByParent(@NotBlank Collection<Long> parentIds) {
        var pointIds = knowledgePointRepository.getChildKnowledgePointIds(parentIds);
        pointIds.addAll(parentIds);
        return pointIds;
    }

    @HandleServiceException
    public Collection<Long> getChildKnowledgePointIds(@NotBlank Collection<Long> parentIds) {
        return knowledgePointRepository.getChildKnowledgePointIds(parentIds);
    }*/

    @CachePut(value = CACHE_NAME, key = "#point.pointId", cacheManager = CacheManagerNames.SIXTY_MINUTES_CACHE)
    @HandleServiceException
    public KnowledgePoint createKnowledgePoint(@NotNull KnowledgePoint point, long operatorId) {
        var existedPoint = knowledgePointRepository.getKnowledgePointByCode(point.getPointCode(), point.getSubjectId());
        if (Objects.nonNull(existedPoint)) {
            ExceptionUtils.throwServiceException(TagsErrorCodes.POINT_CODE_EXISTED, point);
        }
        point.createBy(operatorId);
        return knowledgePointRepository.saveKnowledgePoint(point);
    }

    @CachePut(value = CACHE_NAME, key = "#point.pointId", cacheManager = CacheManagerNames.SIXTY_MINUTES_CACHE)
    @HandleServiceException
    public KnowledgePoint updateKnowledgePoint(@NotNull KnowledgePoint point, long operatorId) {
        var existedPoint = getKnowledgePoint(point.getPointId());
        existedPoint = knowledgePointRepository.getKnowledgePointByCode(point.getPointCode(), point.getSubjectId());
        if (Objects.nonNull(existedPoint) && existedPoint.getPointId() != point.getPointId()) {
            ExceptionUtils.throwServiceException(TagsErrorCodes.POINT_CODE_EXISTED, point);
        }
        point.updateBy(operatorId);
        return knowledgePointRepository.saveKnowledgePoint(point);
    }

    @CacheEvict(value = CACHE_NAME, key = "#pointId")
    @HandleServiceException
    public boolean deleteKnowledgePoint(@Min(0) long pointId, long operatorId) {
        var point = getKnowledgePoint(pointId);
        if (Objects.isNull(point)) return false;
        // 记录操作日志
        point.delete();
        point.updateBy(operatorId);
        knowledgePointRepository.saveKnowledgePoint(point);
        return true;
    }

    @CacheEvict(value = CACHE_NAME, key = "#pointId")
    @HandleServiceException
    public boolean enableKnowledgePoint(@Min(0) long pointId, long operatorId) {
        var point = getKnowledgePoint(pointId);
        if (Objects.isNull(point)) return false;
        // 记录操作日志
        point.enable();
        point.updateBy(operatorId);
        knowledgePointRepository.saveKnowledgePoint(point);
        return true;
    }

    @CacheEvict(value = CACHE_NAME, key = "#pointId")
    @HandleServiceException
    public boolean disableKnowledgePoint(@Min(0) long pointId, long operatorId) {
        var point = getKnowledgePoint(pointId);
        if (Objects.isNull(point)) return false;
        // 记录操作日志
        point.disable();
        point.updateBy(operatorId);
        knowledgePointRepository.saveKnowledgePoint(point);
        return true;
    }
    // endregion


    // region subjectPoint Relation
    /*@HandleServiceException
    public KnowledgePointRelation getKnowledgePointRelation(@Min(0) long id) {
        return knowledgePointRepository.getKnowledgePointRelation(id);
    }

    @HandleServiceException
    public Collection<KnowledgePointRelation> getKnowledgePointRelationsBySource(@Min(0) long sourceId) {
        return knowledgePointRepository.getKnowledgePointRelationsBySource(sourceId);
    }

    @HandleServiceException
    KnowledgePointRelation getKnowledgePointRelation(long sourceId, long targetId, PointRelationTypes relationType) {
        return knowledgePointRepository.getKnowledgePointRelation(sourceId, targetId, relationType);
    }*/

    @HandleServiceException
    public KnowledgePointRelation createKnowledgePointRelation(@NotNull KnowledgePointRelation relation, long operatorId) {
        var existedRelation = knowledgePointRepository.getKnowledgePointRelation(relation.getSourcePointId(), relation.getTargetPointId(),
                PointRelationTypes.valueOf(relation.getRelationTypeId()));
        if (Objects.nonNull(existedRelation)) {
            return existedRelation;
        }
        return knowledgePointRepository.saveKnowledgePointRelation(relation);
    }

    @HandleServiceException
    public KnowledgePointRelation updateKnowledgePointRelation(@NotNull KnowledgePointRelation relation, long operatorId) {
        return knowledgePointRepository.saveKnowledgePointRelation(relation);
    }

    @HandleServiceException
    public Boolean deleteKnowledgePointRelation(@Min(0) long relationId, long operatorId) {
        knowledgePointRepository.deleteKnowledgePointRelation(relationId);
        return true;
    }
    // endregion


    // region misc
    /*@HandleServiceException
    public Collection<KnowledgeTreePoint> getKnowledgeTreePointsBySubject(@Min(0) long subjectId) {
        return knowledgePointRepository.getKnowledgeTreePointsBySubject(subjectId);
    }

    @HandleServiceException
    public Collection<KnowledgeTreePoint> getKnowledgeTreePointsByParent(@Min(0) long subjectId, @Min(0) long parentId) {
        Collection<Long> ids = new ArrayList<>();
        ids.add(parentId);
        Collection<Long> points = getKnowledgePointIdsByParent(ids);
        return knowledgePointRepository.findKnowledgeTreePointsByIds(subjectId, points);
    }

    @HandleServiceException
    public Collection<KnowledgePointRelation> getNonAggregationRelationsBySource(@Min(0) long sourceId) {
        var relations = knowledgePointRepository.getKnowledgePointRelationsBySource(sourceId);
        return relations.stream().filter(r -> r.getRelationTypeId() != PointRelationTypes.AGGREGATION.getValue()).collect(Collectors.toList());
    }*/

    /**
     * reformat the knowledge points recursively (set the values of point level and
     * remark)
     *
     * @param parentId
     * @param pointLevel
     */
    @HandleServiceException
    public boolean arrangeChildKnowledgePoints(@Min(0) long parentId, int pointLevel, long operatorId) {
        var parentPoint = getKnowledgePoint(parentId);
        parentPoint.setPointLevel(pointLevel);
        if (pointLevel == 1)
            parentPoint.setRemark(parentPoint.getPointName());
        updateKnowledgePoint(parentPoint, 0);

        Collection<Long> tempPointIds = new ArrayList<>();
        tempPointIds.add(parentId);
        Collection<Long> childPointIds = knowledgePointRepository.getChildKnowledgePointIds(tempPointIds);
        for (var pointId : childPointIds) {
            var childPoint = getKnowledgePoint(pointId);
            if (childPoint == null) continue;
            childPoint.setRemark(childPoint.getPointName() + "-" + parentPoint.getRemark());
            childPoint.setPointLevel(parentPoint.getPointLevel() + 1);
            updateKnowledgePoint(childPoint, 0);
            arrangeChildKnowledgePoints(childPoint.getPointId(), childPoint.getPointLevel(), operatorId);
        }
        return true;
    }

    /**
     * Moves a subject point to a new parent point
     *
     * @param sourceId
     * @param targetId
     * @param newTargetId
     * @return
     */
    @HandleServiceException
    public boolean moveKnowledgeTreePoint(@Min(0) long sourceId, @Min(0) long targetId, @Min(0) long newTargetId, long operatorId) {
        if (newTargetId == sourceId) return false;
        if (newTargetId == targetId) return false;
        var parentChildRelation = knowledgePointRepository.getKnowledgePointRelation(sourceId, targetId, PointRelationTypes.AGGREGATION);

        if (Objects.nonNull(parentChildRelation)) {
            parentChildRelation.setTargetPointId(newTargetId);
            updateKnowledgePointRelation(parentChildRelation, 0);

            // update point level
            var newParentPoint = getKnowledgePoint(newTargetId);
            var childPoint = getKnowledgePoint(sourceId);
            int childPointLevel;
            if (newParentPoint != null && childPoint != null) {
                childPointLevel = newParentPoint.getPointLevel() + 1;
                childPoint.setPointLevel(childPointLevel);
                childPoint.setRemark(childPoint.getPointName() + "-" + newParentPoint.getRemark());
                updateKnowledgePoint(childPoint, 0);
                // arrange all children points
                // arrangeChildKnowledgePoints(childPoint.getPointId(), childPointLevel);
            }
            return true;
        } else return false;
    }

    /*@HandleServiceException
    public Collection<KnowledgePoint> getRandomKnowledgePoints(@Min(0) long subjectId, int quantity) {
        var points = knowledgePointRepository.getKnowledgePointsBySubject(subjectId);
        Collection<Long> pointIds = points.stream().map(KnowledgePoint::getPointId).collect(Collectors.toList());
        if (pointIds.isEmpty()) return new ArrayList<>();

        var randomPointIds = RandomHelper.getRandomCollection(pointIds, quantity);
        return knowledgePointRepository.getKnowledgePointsByIds(randomPointIds);
    }*/

    // endregion
}
